package resources;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;


public class JogoServerHandler extends IoHandlerAdapter {
	
	public static final boolean DEBUG_ENABLED = true;

	private class JogoClientConnection {
		private IoSession session = null;

		private char id = MotorDoJogo.JOGADOR_VAZIO;
		public void setSession(IoSession session) {
			this.session = session;
		}
		public IoSession getSession() {
			return session;
		}
		public void setId(char id) {
			this.id = id;
		}
		public char getId() {
			return id;
		}
	}

	private List<JogoClientConnection> clients =
				new ArrayList<JogoClientConnection>();


	private int jogadorDaVez = -1;
	

	private MotorDoJogo motorDoJogo = new MotorDoJogo();

	private JogoParser jogoParser = new JogoParser();
	private JogoBean   jogoBean   = new JogoBean();
	
    @Override
    public void sessionOpened(IoSession session) {

    	int clientsSizeBefore = clients.size(); 
    	int clientsSizeAfter  = clientsSizeBefore; 
    	
    	JogoClientConnection client = null;

		if (clientsSizeBefore == 2) {
    		
			if (DEBUG_ENABLED) {
				System.out.println("Conexao com "
						+ session.getRemoteAddress().toString()
						+ " finalizada.");
			}
			
			session.close(false);
            return;
            
    	} else { 


    		client = new JogoClientConnection();
    		client.setSession(session);
    		
    		char idJogador = 
    			(new Random().nextInt(2) == 0) ?
    					MotorDoJogo.JOGADOR_X : MotorDoJogo.JOGADOR_O;
    		
    		switch (clientsSizeBefore) {
    			case 0:
    				client.setId(idJogador);
    				break;  
    			case 1:
    				client.setId(
    						MotorDoJogo.getOponente(clients.get(0).getId()));
    				break;  
    		}
    		clients.add(client);
    		clientsSizeAfter = clients.size();

			if (DEBUG_ENABLED) {
				System.out.println("Jogador " + String.valueOf(client.getId())
						+ " conectou ("	+ getClientAddress(client) + ").");
			}
        	
    		enviarStatusJogo(client, MotorDoJogo.STATUS_AGUARDE);
    		
    	}
    	
    	if (clientsSizeBefore == 1 &&
    		clientsSizeAfter  == 2) { 

    		jogadorDaVez = new Random().nextInt(2);
    		client = clients.get(jogadorDaVez);
    		
			if (DEBUG_ENABLED) {
				System.out.println("Jogador " + String.valueOf(client.getId())
						+ " inicia o jogo.");
			}
			
    		enviarStatusJogo(client, MotorDoJogo.STATUS_JOGUE);
    	}
    }
    
    @Override
    public void sessionClosed(IoSession session) {

    	int clientsSizeBefore = clients.size(); 
    	int clientsSizeAfter  = clientsSizeBefore;
    	
		JogoClientConnection client = null;
		
    	int idx = getIdxBySessao(session);
    	
    	if (idx != -1) {
    		
    		client = clients.get(idx);
    		
			if (DEBUG_ENABLED) {
				System.out.println("Jogador " + String.valueOf(client.getId())
						+ " desconectou (" + getClientAddress(client) + ").");
			}
    		clients.remove(client);
    		clientsSizeAfter = clients.size(); 
    	}
    	
   		if (clientsSizeBefore == 2 &&
   			clientsSizeAfter  == 1) {
   			
   			client = clients.get(0);
    		enviarStatusJogo(client, MotorDoJogo.STATUS_WO);
    		client.getSession().close(true);
    			
   		} else if (clientsSizeBefore == 1 &&
   				   clientsSizeAfter  == 0) {
			if (DEBUG_ENABLED) System.out.println("Aguardando conexoes...");
   		}
   		
   		if (clientsSizeAfter < 2) {
   			jogadorDaVez = -1;
   			motorDoJogo.reorganiza();
   			jogoBean.reorganiza();
   		}
   	}

    @Override
    public void messageReceived(IoSession session, Object message) {
        processarMensagem(session, message.toString());
    }
	
    private void processarMensagem(IoSession session, String message) {

    	if (clients.size() != 2) { return; }
		if (jogadorDaVez != 0 && jogadorDaVez != 1) { return; }

		JogoClientConnection client = null;
		
		int idx = getIdxBySessao(session);
		
		if (idx == -1) { return; }

        if (DEBUG_ENABLED) {
        	client = clients.get(idx);
			System.out.println("  Jogador " + String.valueOf(client.getId())
					+ " (" + getClientAddress(client) + "):");
        	System.out.println("    <= XML recebido: " + message.toString());
        }
        
		if (jogadorDaVez == idx) {

			JogoBean tempBean = jogoParser.getJogoBean(message);
			
			if (verificarJogoBean(tempBean)) {
				executarAcaoJogada(tempBean);
				
			} else {
				client = clients.get(jogadorDaVez);

				if (DEBUG_ENABLED) {
					System.out.println("Jogador "
							+ String.valueOf(client.getId())
							+ " enviou um XML com problemas.");
				}
				
	    		enviarStatusJogo(client, MotorDoJogo.STATUS_JOGUE);
			}
			
		} else {
			client = clients.get(idx);
			
			if (DEBUG_ENABLED) {
				System.out.println("Jogador "
						+ String.valueOf(client.getId())
						+ " jogou na vez errada!");
			}

			enviarStatusJogo(client, MotorDoJogo.STATUS_AGUARDE);
		}
    }

	private void enviarStatusJogo(JogoClientConnection client, String status) {
		
		jogoBean.setId(client.getId());
		jogoBean.setStatus(status);
		jogoBean.setJogada(-1);
		jogoBean.copiarTabuleiro(motorDoJogo.getTabuleiro());

		String jogoXML = jogoParser.getJogoXML(jogoBean);
		if (client.session.isConnected()) {

			client.session.write(jogoXML);
			
			if (DEBUG_ENABLED) {
				System.out.println("  Jogador "
						+ String.valueOf(client.getId()) 
						+ "(" + getClientAddress(client) + "):");
				System.out.println("    => XML enviado: " + jogoXML);
			}
		}
		
	}

	private void executarAcaoJogada(JogoBean jogo) {

		int jogadorProximo = -1;
		if      (jogadorDaVez == 0) jogadorProximo = 1;
    	else if (jogadorDaVez == 1) jogadorProximo = 0;
		
		JogoClientConnection clientVez  = clients.get(jogadorDaVez);
		JogoClientConnection clientProx = clients.get(jogadorProximo);
		
		String statusVez  = MotorDoJogo.STATUS_AGUARDE;
		String statusProx = MotorDoJogo.STATUS_JOGUE;

		motorDoJogo.setPosicao(jogo.getJogada(), jogo.getId());

		if (DEBUG_ENABLED) {
			System.out.println("Jogador "
					+ String.valueOf(clientVez.getId())
					+ " jogou na posicao " + jogo.getJogada());
		}
		
		if (motorDoJogo.isEmpate()) {
			statusVez  = MotorDoJogo.STATUS_EMPATE;
			statusProx = MotorDoJogo.STATUS_EMPATE;

			if (DEBUG_ENABLED) System.out.println("Houve empate!");

		} else if (motorDoJogo.isGanhador(clientVez.getId())) {
			statusVez  = MotorDoJogo.STATUS_GANHOU;
			statusProx = MotorDoJogo.STATUS_PERDEU;

			if (DEBUG_ENABLED) {
				System.out.println("Jogador "
						+ String.valueOf(clientVez.getId())	+ " ganhou!");
			}
		}
		
		jogadorDaVez = jogadorProximo;
		
		enviarStatusJogo(clientVez, statusVez);
		enviarStatusJogo(clientProx, statusProx);
		
		if (motorDoJogo.isGameOver()) {
			clientVez.getSession().close(true);
			clientProx.getSession().close(true);

			if (DEBUG_ENABLED) System.out.println("Game over!");
		}
	}

	private int getIdxBySessao(IoSession session) {
		for (int i = 0; i < clients.size(); i++) {
			if (clients.get(i).session == session) {
				return i;
			}
		}
		return -1;
	}

	private boolean verificarJogoBean(JogoBean jogo) {

		if (jogo == null) return false;

		if (jogo.getId() != clients.get(jogadorDaVez).getId()) {
			return false;
		}

		int jogada = jogo.getJogada();
		if (jogada < 0
			  || motorDoJogo.getPosicao(jogada) != MotorDoJogo.JOGADOR_VAZIO) {
				return false;
		}
		return true;
	}
	
	private String getClientAddress(JogoClientConnection client) {
		String address = "";
		if (client != null && client.getSession() != null
				&& client.getSession().getRemoteAddress() != null) {
			address = client.getSession().getRemoteAddress().toString();
		}
		return address;
	}
	
	@Override
	public void exceptionCaught(IoSession session, Throwable cause) {

		if (DEBUG_ENABLED) cause.printStackTrace();
		session.close(true);
	}
}
